Implement 2D minkowski() as a boundary sweep - #89
Merged
Conversation
It only ever looked at 3D bodies. 2D sections among its children were
dropped on the floor, silently, so
linear_extrude(6) minkowski() { square([30,20]); circle(4); }
produced no geometry at all where the reference produces the rounded
square asked for. No warning either -- the whole statement simply
vanished from the model.
Manifold has no 2D Minkowski, so this builds one: cut both shapes into
convex pieces, take the convex hull of every pairwise sum of points, and
union the results. Correct because the sum of two convex sets is the hull
of their pairwise sums, and Minkowski distributes over union. A convex
outline with no holes stays whole rather than being triangulated, which
is the case that actually turns up -- a circle swept over something --
and much cheaper.
Matches the reference exactly on both a convex and a concave outline:
same facet count (108 and 100) and the same volume to five figures.
A mix of 2D and 3D children still takes the 3D path, as before.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The first version cut both operands into convex pieces and hulled every
pair. The boundary-sweep identity is better: for B convex and containing
the origin,
A (+) B = A union (boundary of A (+) B)
and the boundary is a chain of segments, each of which sums with a convex
B to the hull of B at its two ends. So A is never cut up at all, however
concave it is or however many holes it has -- only its edges are walked.
Only B is decomposed, and only because the per-segment hull needs it
convex.
Both conditions on B are met rather than assumed, and each is its own
test because each is silently wrong on its own:
hulling B instead of decomposing it computes A (+) hull(B), 5092
against the reference's 4992
leaving B where it is, when it does not contain the origin, keeps an
untranslated copy of A: the result starts at x=0 where the reference
starts at x=16
walking one contour leaves a hole unswept
Matches the reference exactly on all six: convex, concave A, concave B,
an off-origin B, three operands, and a shape with a hole.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
minkowski()only ever looked at 3D bodies. 2D sections among its children were dropped on the floor — silently:produced no geometry at all, where the reference produces the rounded square asked for. No warning; the whole statement just vanished from the model. Found while writing an example for
minkowski(), which rendered two of its three copies and nothing where that one should have been.How
Manifold has no 2D Minkowski, so this builds one from the boundary-sweep identity: for
Bconvex and containing the origin,and
∂Ais a chain of segments, each of which sums with a convexBto the hull ofBat its two ends. SoAis never cut up at all — however concave it is, however many holes it has, only its edges are walked. OnlyBis decomposed, and only because the per-segment hull needs it convex.That is one hull per edge of
Aper convex piece ofB, and the piece count is one in the case that actually turns up: a circle swept over something.Both conditions on
Bare met rather than assumed, and each is silently wrong on its own, so each has its own test:B(hulling it instead)A ⊕ hull(B)— 5092 against the reference's 4992Bonto the origin and backA: starts at x=0 where the reference starts at x=16Matching the reference
Six cases, all matching the reference exactly:
A mix of 2D and 3D children still takes the 3D path, as before.
Testing
Four tests: a convex outline rounded to the right size and bounding box, a concave one that must stay concave, a single section handed back untouched, and 3D still working.
The concave test took three attempts to become worth having, and the negative control is why. Substituting
Hull(parts)for the union — the obvious wrong implementation — first passed a volume bound, because hulling lands within 1% of the right answer on this shape. It then passed a probe of the notch, because I had put the probe outside both shapes, where it proved nothing. It now sits inside what a hull would cover and outside the real shape, and catches the substitution.825 tests pass. Negative-controlled: hulling the sweeper, skipping the origin shift, and walking only the first contour each fail exactly their own test and nothing else.
🤖 Generated with Claude Code